﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using dotless.Core;
using dotless.Core.configuration;
using dotless.Core.Input;
using dotless.Core.Parser;

namespace InteSoft.Web.ExternalResourceLoader
{
    public class ExternalResourceController : Controller
    {
        public ExternalResourceController()
        {
            TempDataProvider = new NullTempDataProvider();
        }

        [NonAction]
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                "ExternalResource",
                "ExternalResource/{name}/{version}/{display}/{condition}",
                new { controller = "ExternalResource", action = "Index", condition = "" }
            );
        }

        [NonAction]
        public static void RegisterRoutes(RouteCollection routes, string rootFolder)
        {
            routes.MapRoute(
                "ExternalResource",
                rootFolder,
                new { controller = "ExternalResource", action = "Index" }
            );
        }

        public void Index(string name, string version, Action display, string condition)
        {
            HttpResponseBase response = Response;

            ConfigurationSection section = ConfigurationManager.GetSection("externalResources") as ConfigurationSection;
            if (section == null)
            {
                throw new HttpException(500, "Unable to find any external resource settings.");
            }

            ReferenceElement settings = section.References[name];

            if (settings == null)
            {
                throw new HttpException(500, string.Format("Unable to find any matching external resource settings for {0}.", name));
            }

            Condition conditionInfo = new Condition
            {
                Action = display,
                If = condition ?? string.Empty
            };

            // filter the files based on the condition (Action / If) passed in
            IList<FileInfo> files = new List<FileInfo>();
            foreach (FileInfo fileInfo in settings.Files)
            {
                if (fileInfo.Action.Equals(conditionInfo.Action) && fileInfo.If.Equals(conditionInfo.If))
                {
                    files.Add(fileInfo);
                }
            }

            // Ooutput Type
            response.ContentType = settings.MimeType;
            Stream output = response.OutputStream;

            // Compress
            if (section.Compress)
            {
                string acceptEncoding = Request.Headers["Accept-Encoding"];

                if (!string.IsNullOrEmpty(acceptEncoding))
                {
                    acceptEncoding = acceptEncoding.ToLowerInvariant();

                    if (acceptEncoding.Contains("gzip"))
                    {
                        response.AddHeader("Content-encoding", "gzip");
                        output = new GZipStream(output, CompressionMode.Compress);
                    }
                    else if (acceptEncoding.Contains("deflate"))
                    {
                        response.AddHeader("Content-encoding", "deflate");
                        output = new DeflateStream(output, CompressionMode.Compress);
                    }
                }
            }

            // Combine
            var dotLessEngineFactory = new EngineFactory(DotlessConfiguration.DefaultWeb);
            var dotLessEngine = dotLessEngineFactory.GetEngine();

            using (StreamWriter sw = new StreamWriter(output))
            {
                foreach (FileInfo fileInfo in files)
                {
                    string filepath = Server.MapPath(fileInfo.Filename);
                    string content = System.IO.File.ReadAllText(filepath);

                    if (fileInfo.Filename.EndsWith(".less", StringComparison.InvariantCultureIgnoreCase))
                    {
                        // this construct is to enable us to refer to the relativePath above ...
                        var urlDelegate = new MatchEvaluator(delegate(Match m)
                        {
                            // Change relative (to the original CSS) URL references to make them relative to the requested URL (controller / action)
                            string url = m.Value;

                            Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);

                            if (uri.IsAbsoluteUri || VirtualPathUtility.IsAbsolute(url))
                            {
                                // if the URL is absolute ("http://server/path/file.ext") or app absolute ("/path/file.ext") then leave it as it is
                            }
                            else
                            {
                                // get app relative url
                                url = VirtualPathUtility.Combine(fileInfo.Filename, url);
                                url = VirtualPathUtility.MakeRelative(Request.Url.AbsolutePath, url);
                            }

                            return url;
                        });
                        content = Regex.Replace(content, @"(?<=@import\s*([""']))(?<url>.+?)(?=\1\s*;)", urlDelegate, RegexOptions.IgnoreCase);
                        content = dotLessEngine.TransformToCss(content, fileInfo.Filename);
                    }

                    if (section.Compact)
                    {
                        switch (settings.MimeType)
                        {
                            case "text/css":
                                content = CSSMinify.Minify(Url, fileInfo.Filename, Request.Url.AbsolutePath, content);
                                break;
                            case "text/x-javascript":
                            case "text/javascript":
                            case "text/ecmascript":
                                content = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(content);
                                break;
                        }
                    }
                    sw.WriteLine(content.Trim());
                }
            }

            // Cache

            if (section.CacheDuration > 0)
            {
                DateTime timestamp = HttpContext.Timestamp;
                HttpCachePolicyBase cache = response.Cache;
                int duration = section.CacheDuration;

                cache.SetCacheability(HttpCacheability.Public);
                cache.SetExpires(timestamp.AddSeconds(duration));
                cache.SetMaxAge(new TimeSpan(0, 0, duration));
                cache.SetValidUntilExpires(true);
                cache.SetLastModified(timestamp);
                cache.VaryByHeaders["Accept-Encoding"] = true;
                cache.VaryByParams["name"] = true;
                cache.VaryByParams["version"] = true;
                cache.VaryByParams["display"] = true;
                cache.VaryByParams["condition"] = true;
                cache.SetOmitVaryStar(true);
            }
        }
    }
}
